#include "head.h"
#include "lua_epoll.h"

/*
    usage:  setnonblocking(fd)
    paras:  fd
    return: fd
*/
static int l_setnonblocking(lua_State *L) {
    int flag, fd;
    fd = luaL_checkinteger(L, 1);
    
    if ((flag = fcntl(fd, F_GETFL, 0)) == -1) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }
    flag |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flag) == -1) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }
    lua_pushboolean(L, true);
    return 1;
}

/*
    usage:  create()
    paras:  \
    return: [epfd]、[nil, errno]
*/
static int l_epoll_create(lua_State *L) {
    int epfd;
    if ((epfd = epoll_create(1)) == -1) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }
    lua_pushinteger(L, epfd);
    return 1;
}

/*
    usage:  register()
    paras:  [epfd, fd, mask]
    return: [true]、[nil, errno]
*/
static int l_epoll_add(lua_State *L) {
    int epfd, fd, eventmask;

    epfd = luaL_checkinteger(L, 1);
    fd = luaL_checkinteger(L, 2);
    eventmask = luaL_checkinteger(L, 3);

    struct epoll_event ev; 
    ev.data.fd = fd;
    ev.events = eventmask; 

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    lua_pushboolean(L, true);
    return 1;
}

/*
    usage:  modify()
    paras:  [epfd, fd, mask]
    return: [true]、[nil, errno]
*/
static int l_epoll_mod(lua_State *L) {
    int epfd, fd, eventmask;

    epfd = luaL_checkinteger(L, 1);
    fd = luaL_checkinteger(L, 2);
    eventmask = luaL_checkinteger(L, 3);

    struct epoll_event ev; 
    ev.data.fd = fd;
    ev.events = eventmask; 

    if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    lua_pushboolean(L, true);
    return 1;
}

/*
    usage:  unregister()
    paras:  [epfd, fd]
    return: [true]、[nil, errno]
*/
static int l_epoll_del(lua_State *L) {
    int epfd, fd;

    epfd = luaL_checkinteger(L, 1);
    fd = luaL_checkinteger(L, 2);

    if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    lua_pushboolean(L, true);
    return 1;
}

/*
    usage:  close()
    paras:  [epfd]
    return: [true]、[nil, errno]
*/
static int l_epoll_close(lua_State *L) {
    int fd;
    fd = luaL_checkinteger(L, 1);
    if (close(fd) == -1) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }
    lua_pushboolean(L, true);
    return 1;
}

/*
    usage:  wait()
    paras:  [epfd, timeout, max_events]
    return: [{"fd":events}]、[nil, errno]
*/
static int l_epoll_wait(lua_State *L) {
    int epfd, timeout, max_events; 
    int n;

    epfd = luaL_checkinteger(L, 1);
    timeout = luaL_checkinteger(L, 2);
    max_events = luaL_checkinteger(L, 3);

    struct epoll_event events[max_events];
    if ((n = epoll_wait(epfd, events, max_events, timeout)) == -1) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }
    lua_newtable(L);
    for (int i = 0; i < n; i++) {
        lua_pushinteger(L, events[i].data.fd);
        lua_pushinteger(L, events[i].events);
        lua_settable(L, -3); // t[fd] = events
    }
    return 1;
}

int lua_lib_epoll(lua_State *L) {
    static const luaL_Reg l[] = {
        { "setnonblocking", l_setnonblocking },
        { "create", l_epoll_create },
        { "close", l_epoll_close },
        { "wait", l_epoll_wait },
        { "register", l_epoll_add },
        { "unregister", l_epoll_del },
        { "modify", l_epoll_mod },
        { NULL, NULL }
    };
    luaL_newlib(L, l);

#define SETCONST(EVENT) \
    lua_pushnumber(L, EVENT); \
    lua_setfield(L, -2, #EVENT) \

//  epoll_ctl: 
    SETCONST(EPOLLIN);
    SETCONST(EPOLLOUT);
    SETCONST(EPOLLRDHUP);
    SETCONST(EPOLLPRI);
    SETCONST(EPOLLERR);
    SETCONST(EPOLLHUP);
    SETCONST(EPOLLET);
    SETCONST(EPOLLONESHOT);
    SETCONST(EPOLLWAKEUP);
    SETCONST(EPOLLEXCLUSIVE);

    return 1;
}